/**
* @author maze | guavestudios.com
* @license MIT License see LICENSE.TXT
* @version 0.1
*/

package com.guavestudios.fcon {
	//TODO CLEANUP
	import com.guavestudios.fcon.format.IScrollableAppender;
	import com.guavestudios.fcon.util.KeyMatcher;
	import flash.display.*;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.TextEvent;
	import flash.media.SoundTransform;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.text.*;
	import flash.ui.Keyboard;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.xml.XMLNode;
	import com.guavestudios.fcon.core.ConsoleOrientation;
	import com.guavestudios.fcon.events.*;
	import com.guavestudios.fcon.core.*;
	import com.guavestudios.fcon.format.DefaultAppender;
	import com.guavestudios.fcon.format.IAppender;
	import com.guavestudios.fcon.layout.ConsoleBackground;
	import com.guavestudios.fcon.layout.ConsoleLook;
	import com.guavestudios.fcon.layout.DefaultLayout;
	
	import flash.utils.describeType;
	import com.guavestudios.fcon.api.*;
	import com.guavestudios.fcon.commands.*;
	import com.guavestudios.fcon.plugins.*;
	
	/**
	* Dispatched when the console is opened
	* This event is cancelable
	*/
	[Event(name="CONSOLE_TOGGLE",type="com.guavestudios.fcon.events.ConsoleExecuteEvent")]
	/**
	* Dispatched a command is executed
	*/
	[Event(name="EXECUTE",type="com.guavestudios.fcon.events.ConsoleExecuteEvent")]
	/**
	* Dispatched when a command failed
	*/
	[Event(name="EXECUTE_CANCELD",type="com.guavestudios.fcon.events.ConsoleExecuteEvent")]
	/**
	* Dispatched when a command was successfully exexuted
	*/
	[Event(name="EXECUTE_OK",type="com.guavestudios.fcon.events.ConsoleExecuteEvent")]
	/**
	* Dispatched when the user send a command with the input
	*/
	[Event(name="EXECUTE_USER",type="com.guavestudios.fcon.events.ConsoleExecuteEvent")]
	/**
	* Dispatched when the user type in the input TextField
	*/
	[Event(name="INPUT_KEYUP",type="com.guavestudios.fcon.events.ConsoleKeyboardEvent")]
	
	
	//import org.flashdevelop.utils.FlashConnect;
	/**
	* Implicit class implementation of AbstractConsole
	*/
	public class ConsoleImpl extends AbstractConsole {
		
		//{ STATIC stuff
		private static var stage:Stage;
		private static var instance:ConsoleImpl;
		
		/**
		* get the current instance of the console
		* @return IConsole
		*/
		public static function getInstance():ConsoleImpl
		{
			if (instance==null) instance=new ConsoleImpl();
			return instance;
		}
		/**
		* Sets the stage for console
		* Will automatically place itself on top
		* @param	s
		*/
		public static function setStage(s:Stage,config:XML=null):void
		{
			if (stage!=null) return;
			
			stage=s;
			if (config)
				getInstance().init(config);
			else
				getInstance().init(<root />);
			
		}
		/**
		* trace a string to console
		* @param	s	text to trace
		*/
		public static function trace(s:String):void
		{
			instance.trace(s);
		}
		/**
		* Execute a command in console
		* @param	s string or an command array
		*/
		public static function execute(s:*):void
		{
			instance.execute(s);
		}
		//}
		
		//[Embed(source="../commands.swf/library.swf")]
		//[Embed(source="../../../../describeType.xml", mimeType="application/octet-stream")]
		//private var imp_lib:Class;
		
		private var input:TextField;
		private var output:TextField;
		private var isshow:Boolean=false;
		private var openKey:KeyMatcher;
		private var _constants:IConstantsProvider;
		//{ GETTER-SETTER
		
		//}

		public function ConsoleImpl() {
			super();
			this.name = "fcon";
			config=<settings>
					<setting id="Console.OpenKey"><![CDATA[crtl+Key.220]]></setting>
					<setting id="Console.trace">false</setting>
					<setting id="AutoCompletion.Key"><![CDATA[Key.TAB]]></setting>
					<setting id="HistoryManager.SaveHistory">false</setting>
					<setting id="Script.SaveScript">false</setting>
				</settings>;
			
			this.visible=false;
		}

		public function init(_config:XML=null):void
		{
			if (_config) 
			{
				var lst:XMLList = _config.setting;
				Trace.tracex("len: " + lst.length());
				Trace.tracex(lst);
				for each (var x:XML in lst)
				{
					if (config.*.(@id == x.@id).length()>0)
					{
						config.*.(@id == x.@id)[0].setChildren(x.children());
						Trace.tracex("override: " + x.@id + " - " + config.*.(@id == x.@id)[0]);
					}
				}
			}
			initConfig(config);
			
			output=new TextField();
			this.addChild(output);	
			input=new TextField();
			this.addChild(input);
			
			ConsoleImpl.stage.addChild(this);
			ConsoleImpl.stage.addEventListener(Event.RESIZE,stage_resize);
			ConsoleImpl.stage.addEventListener(KeyboardEvent.KEY_UP,stage_keyboard);
			
			
			
			
			//initialize the appender
			var a:DefaultAppender=new DefaultAppender();
			a.setOutput(output);
			a.setTrace(getConfig("Console.trace") == "true");
			appender=a;
			
			
			//history=new HistoryManager();
			pathParser=new PathParser();
			//pathParser.setNavigateObject(stage);
			
			commands=new CommandManager();
			pluginManager=new PluginManager();
			
			
			//init the pluginmanager
			pluginManager.setConsole(this);
			
			//set the console look
			consoleLook=new ConsoleLook(new DefaultLayout(output,input),new ConsoleBackground(),this);
			
			redraw();
			
			loadFeatures();
			
			
		}
		
		private function initConfig(xml:XML):void{
			super.config = xml;
			
			//key config
			
			var s:String = String(getConfig("Console.OpenKey"));
			openKey = KeyMatcher.createMatcher(s);
		}
		
		//{ PUBLIC FUNCTIONS
		
		//GENERAL
		override public function getStage():Stage
		{
			return ConsoleImpl.stage;
		}
		override public function execute(s:*):void
		{
			var a:Array;
			if (s is String)
			{
				//var cs:String=String(s);
				//if (cs.length>2)
				//{
				//	a=cs.split(" ",10);
				//}
				a=parseExecute(s);
			}
			else
			{
				a=s;
			}
			
			if (a[0] is String)
			{
				
				//first dispatch the event
				var event:ConsoleExecuteEvent=new ConsoleExecuteEvent(ConsoleExecuteEvent.EXECUTE,this,a);
				dispatchEvent(event);
				if (event.Cancel) return; //check the allow execute
				
				var m:INamedCommand=commands.getCommand(a[0]);
				if (m!=null)
				{
					a.shift();
					
					var pa:Array;
					for (var i:int=0; i<a.length; i++)
					{
						if (a[i] is String)
						{
							pa=pathParser.parse(a[i]);
							if (pa!=null)
							{
								a[i]=pa[pa.length-1];
							}
						}
					}
					
					a.unshift(this);
					m.execute(a);
				}
				else
				{
					outputText("execute: Unknown command");
				}
			}
			else
			{
				outputText("execute: Parameters not valid");
			}
			
			dispatchEvent(new ConsoleExecuteEvent(ConsoleExecuteEvent.EXECUTE_OK,this,a));
			//history.addHistory(s.toString());
		}
		public function parseExecute(s:String):Array {
			var a:Array = [];
			
			var char:String;
			var chars:String="";
			var esc:Boolean = false;
			var str:Boolean = false;
			for (var i:int = 0, ilen:int = s.length; i < ilen; i++) {
				char = s.charAt(i);
				
				if (esc) {
					esc = false;
					chars += char;
				} else if (char == "\\") {
					if (!esc) {
						esc = true;
					}
					
				} else if (char == "\"") {
					if (str) {
						a.push(chars);
						chars = "";
					}
					str = !str;
				} else if (char==" ") {
					if (chars.length > 0) {
						a.push(chars);
						chars = "";
					}
				}  else {
					chars += char;
				}
				
				if (i == ilen - 1)
					if (chars.length > 0)
						a.push(chars);
			}
			return a;
		}
		public function show():void
		{
			if (!isshow)
			{
				toggle();
			}
		}
		public function hide():void
		{
			if (isshow)
			{
				toggle();
			}
			
		}
		public function toggle():void
		{
			var e:ConsoleExecuteEvent=new ConsoleExecuteEvent(ConsoleEvent.CONSOLE_TOGGLE,this,null);
			dispatchEvent(e);
			if (e.Cancel) return; //check if allowed
			
			if (!isshow) 
			{
				this.visible=isshow=true;
				redraw();
				stage.focus=input;
			}
			else
			{
				this.visible=isshow=false;
				redraw();
			}
			
		}
		public function isShow():Boolean
		{
			return isshow;
		}
		public function clear():void
		{
			appender.clear();
		}
		public function setInputText(s:String):void
		{
			input.text=s;
		}
		public function getInput():TextField //DEBUG: may be the wrong thing
		{
			return input;
		}
		public function getOutput():TextField
		{
			return output;
		}
		public function setInputFocus():void
		{
			stage.focus=input;
		}
		override public function getOpenKey():KeyMatcher
		{
			return openKey;
		}
		
		//}
		
		//{ Private functions
		private function stage_resize(e:Event):void
		{
			redraw();
		}
		private function stage_keyboard(e:KeyboardEvent):void
		{
			
			//outputText(e.keyCode.toString());
			if (openKey.matchEvent(e)) //on german ^
			{
				toggle();
				return;
			}
			else if (isshow)
			{
				if (stage.focus===input) //input is focused
				{
					dispatchEvent(new ConsoleKeyboardEvent(ConsoleKeyboardEvent.INPUT_KEYUP,this,e));
					
					switch(e.keyCode)
					{
						case Keyboard.ENTER:
							if (input.length>2)
							{
								dispatchEvent(new ConsoleExecuteEvent(ConsoleExecuteEvent.EXECUTE_USER,this,[input.text]));
								execute(input.text);
								input.text="";
							}
							break;
						case Keyboard.PAGE_UP:
							if (appender is IScrollableAppender)
								if (e.shiftKey) IScrollableAppender(appender).scrollTop();
								else IScrollableAppender(appender).scrollUp();
							break;
						case Keyboard.PAGE_DOWN:
							if (appender is IScrollableAppender)
								if (e.shiftKey) IScrollableAppender(appender).scrollBottom();
								else IScrollableAppender(appender).scrollDown();
							break;
					}
				}
				
			}
		}
		private function loadFeatures():void
		{
			pluginManager.addPlugin(new ConstantsPlugin());
			pluginManager.addPlugin(new HistoryManager());
			pluginManager.addPlugin(new LockManager());
			pluginManager.addPlugin(new AutoCompletion());
			var auto:AutoStartPlugin = new AutoStartPlugin();
			pluginManager.addPlugin(new SimpleScripter());
			pluginManager.addPlugin(auto);
			var editor:SimpleEditor = new SimpleEditor();
			pluginManager.addPlugin(editor);
			
			
			
			//add the default commands
			commands.addCommand(new ClearCommand());
			commands.addCommand(new ConstantCommand() );
			commands.addAlias("const", commands.getCommand("constant"));
			commands.addAlias("set", commands.getCommand("constant"));
			//commands.addCommand(new TestCommand());
			commands.addCommand(new ModifyCommand());
			commands.addCommand(new PrintCommand());
			commands.addAlias("p",commands.getCommand("print"));
			commands.addCommand(new AlignCommand());
			commands.addCommand(new LoadCommandCommand());
			
			
			//has constants
			if (pluginManager.hasPlugin("ConstantsPlugin"))
			{
				_constants = IConstantsProvider(pluginManager.getPlugin("ConstantsPlugin"));
				pathParser.constants = _constants;
				_constants.setConstant("stage",stage);
			}
			
			//finally execute the autostart
			auto.executeAutostart();
		}

		//}
		
	}
	
}
